package com.example.scg.config;

import java.util.Optional;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cloud.gateway.config.GlobalCorsProperties;
import org.springframework.cloud.gateway.handler.FilteringWebHandler;
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.core.env.Environment;
import org.springframework.web.server.ServerWebExchange;
import reactor.cache.CacheMono;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Signal;

/**
 * CreateTime: 2021-06-15
 *
 * @author HuangZhongHui
 **/
public class CustomRoutePredicateHandlerMapping extends RoutePredicateHandlerMapping {

  private final Cache specialCache;

  public CustomRoutePredicateHandlerMapping(
      CacheManager cacheManager,
      FilteringWebHandler webHandler,
      RouteLocator routeLocator,
      GlobalCorsProperties globalCorsProperties,
      Environment environment) {
    super(webHandler, routeLocator, globalCorsProperties, environment);
    specialCache = cacheManager.getCache("specialRouteCache");
  }

  @Override
  protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
    //1. 从exchange中获取请求特征，如path
    //2. 如果符合特征 则使用缓存，从缓存获取，如果缓存未命中，
    //   调用 super.lookupRoute(exchange) 并加入缓存
    //3. 不符合特征的，直接调用

    // 下面演示使用 caffeine 缓存的方式
    String specialPath = exchange.getRequest().getPath().subPath(0).value();
    // 判断path是否符合缓存规则（一般而言用于仅采用Path断言，或简单结合header或query的情况，下面以只有path为例）
    if (checkPath(specialPath)) {
      return CacheMono
          // 查找缓存
          .lookup(
              key -> Mono.justOrEmpty(specialCache.get(key, Route.class)).map(Signal::next),
              toKey(specialPath))
          // 未命中直接查找路由表
          .onCacheMissResume(
              () -> super.lookupRoute(exchange))
          // 然后写到缓存
          .andWriteWith(
              (key, signal) -> Mono.fromRunnable(
                  () -> Optional.ofNullable(signal.get())
                      .ifPresent(value -> specialCache.put(key, value))
              ));
    }
    return super.lookupRoute(exchange);
  }

  /**
   * 校验请求特征的方法，此处仅是举例
   */
  private boolean checkPath(String path) {
    return true;
  }

  /**
   * 生成cacheKey的方式，此处仅是举例
   */
  private String toKey(String specialPath) {
    return specialPath;
  }
}
